﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;

namespace MicroMVVM
{
    public class Bootstrapper
    {
        /// <summary>
        /// Indicates whether the Bootstrapper is running already.
        /// </summary>
        private bool _isExecuted;

        /// <summary>
        /// Lock on the _isExecuted boolean.
        /// </summary>
        private readonly object _lockIsExecuted = new object();

        /// <summary>
        /// By default the conditional compilation symbol is assumed to be runtime if not stated otherwise.
        /// </summary>
        private ServiceType _conditionalServiceType = ServiceType.Runtime;

        /// <summary>
        /// A list of assemblies retrieved from the installed location.
        /// </summary>
        private Assembly[] Assemblies { get; set; }

        /// <summary>
        /// The list of exportable services.
        /// </summary>
        private ICollection<Type> ExportableServices { get; set; }

        /// <summary>
        /// The list of exportable view models.
        /// </summary>
        private ICollection<Type> ExportableViewModels { get; set; }

        /// <summary>
        /// The environment from which the bootstrapper is called.
        /// </summary>
        public static Bootstrapper Environment { get; private set; }

        /// <summary>
        /// Bootstrapper static entry point.
        /// </summary>
        static Bootstrapper()
        {
            Environment = new Bootstrapper();
        }

        /// <summary>
        /// Launch Bootstrapper in Runtime.
        /// </summary>
        public void Run()
        {
            lock (_lockIsExecuted)
            {
                if (_isExecuted) return;

                _isExecuted = true;
            }

            System.Diagnostics.Debug.WriteLine(string.Format("Bootstrapper launched in {0}.", _conditionalServiceType));

            LoadAssemblies();

            LoadExportableServices<ExportService>();

            LoadExportableViewModels<ExportViewModel>();
        }

        /// <summary>
        /// Launch Bootstrapper in Designtime.
        /// </summary>
        public void RunDesigntime()
        {
            _conditionalServiceType = ServiceType.Designtime;

            Run();
        }

        /// <summary>
        /// Loads assemblies from installed location.
        /// </summary>
        private void LoadAssemblies()
        {
            Assemblies = AppDomain.CurrentDomain.LoadAssemblies();
        }

        /// <summary>
        /// Loads the exportable services
        /// </summary>
        /// <typeparam name="T"></typeparam>
        private void LoadExportableServices<T>()
        {
            ExportableServices = GenericLoader<T>();

            GetInterfaces();
        }

        /// <summary>
        /// Loads the exportable view models.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        private void LoadExportableViewModels<T>()
        {
            ExportableViewModels = GenericLoader<T>();

            GetViewModels();
        }

        /// <summary>
        /// Generic loader from assemblies the exportable.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        private Collection<Type> GenericLoader<T>()
        {
            var exportables = new Collection<Type>();

            foreach (var assembly in Assemblies)
            {
                var validExportables = assembly.ExportedTypes.Where(
                    type => type.GetTypeInfo().CustomAttributes.Any(a => a.AttributeType == typeof (T)));

                foreach (var exportable in validExportables)
                {
                    System.Diagnostics.Debug.WriteLine(string.Format("Found valid exportable {0}.", exportable));

                    exportables.Add(exportable);
                }
            }

            return exportables;
        }

        /// <summary>
        /// Gets the corresponding interfaces for the exportable services.
        /// </summary>
        private void GetInterfaces()
        {
            foreach (var exportable in ExportableServices)
            {
                ExportService exportService;

                if (InternalHelpers.TryGetAttribute(exportable.GetTypeInfo(), out exportService) == false) return;

                var exportType = exportService.ExportType;
                var serviceType = exportService.ServiceType;
                var partCreationPolicy = exportService.PartCreationPolicy;

                if ((serviceType != _conditionalServiceType) && (serviceType != ServiceType.Any)) continue;

                System.Diagnostics.Debug.WriteLine(string.Format("Registering service {0} of type {1} for {2} execution and part creation policy {3}.", exportable, exportType, serviceType, partCreationPolicy));

                Container.RegisterService(exportType, exportable);
            }
        }

        /// <summary>
        /// Gets the exportable view models.
        /// </summary>
        private void GetViewModels()
        {
            foreach (var exportable in ExportableViewModels)
            {
                ExportViewModel exportViewModel;

                if (InternalHelpers.TryGetAttribute(exportable.GetTypeInfo(), out exportViewModel) == false) return;

                var identifier = exportViewModel.Identifier;

                System.Diagnostics.Debug.WriteLine(string.Format("Mapping ViewModel {0} with View identifier {1}.", exportable, identifier));

                Container.MapViewModel(identifier, exportable);
            }
        }
    }
}